/*
 * Copyright 2002-2005 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package lib.jsonrpc;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import java.util.Map;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;



// TODO: Auto-generated Javadoc
/**
 * The Class BasicRPCService is base class for the RPC service to extends. The
 * subclass implements some interface, these methods of interface can be invoked 
 * from the client sides. The subclass is able to get request or response when 
 * invoked, which is thread safe by thread local variables.
 * 
 * @author jay liu
 * @since 02.10.2010
 */
public class BasicRPCService implements RPCService{
	
	/** The Constant log. */
	private static final Logger log = Logger.getLogger(BasicRPCService.class.getName());
	

	/** The methods. */
	private Map<String, Method> methods = new HashMap<String, Method>();

	/** The request. */
	private ThreadLocal<HttpServletRequest> request = new ThreadLocal<HttpServletRequest>();

	/** The response. */
	private ThreadLocal<HttpServletResponse> response = new ThreadLocal<HttpServletResponse>();

	/** The app id. */
	private ThreadLocal<String> appId = new ThreadLocal<String>();

	/** The signature. */
	private ThreadLocal<String> signature = new ThreadLocal<String>();

	/** The arguments. */
	private ThreadLocal<JSONArray> arguments = new ThreadLocal<JSONArray>();
	
	
	/** The factory. */
	private ObjectFactory factory = new BasicObjectFactory();
	
	
	
	/**
	 * Sets the object factory.
	 *
	 * @param factory the new object factory
	 */
	public void setObjectFactory(ObjectFactory factory){
		this.factory = factory;
	}

	/* (non-Javadoc)
	 * @see lib.jsonrpc.RPCService#getObjectFactory()
	 */
	@Override
	public ObjectFactory getObjectFactory() {
		return factory;
	}

	/* (non-Javadoc)
	 * @see lib.jsonrpc.RPCService#getServiceName()
	 */
	@Override
	public String getServiceName() {
		return this.getClass().getSimpleName();
	}
	
	/**
	 * Gets the method.
	 *
	 * @param methodName the method name
	 * @return the method
	 */
	private Method getMethod(String methodName) {
		if (methods.containsKey(methodName))
			return methods.get(methodName);

		Method[] ms = this.getClass().getMethods();
		for (Method m : ms) {
			if (m.getName().equals(methodName)) {
				methods.put(methodName, m);
				return m;
			}
		}
		return null;
	}

	
	/**
	 * Dispatch.
	 *
	 * @param req the req
	 * @param resp the resp
	 */
	public void dispatch(HttpServletRequest req, HttpServletResponse resp) {
		String jsonrpc = null;
		int id = 0;
		try {
			
			try {
				JSONObject jreq = parseJSON(req);
				jsonrpc = jreq.getString("jsonrpc");
				String methodName = jreq.getString("method");
				JSONArray jargs = jreq.getJSONArray("params");
				id = jreq.optInt("id");
				

				Method method = getMethod(methodName);
				if (method == null)
					throw new RPCException(RPCException.METHOD_NOT_FOUND);

				Object[] args;

				args = Utils.parseArguments(method, jargs, getObjectFactory());

				request.set(req);
				response.set(resp);
				
				arguments.set(jargs);

				beforeInvoke(method, args);
				Object retObj = method.invoke(this, args);
				afterInvoke(method, args, retObj);
				log.info("---------invoke:" + jreq.toString());
				JSONObject ret = Utils.encodeReturnValue(method, retObj);
				log.info("---------invoke ret:" + ret.toString());
				ret.put("jsonrpc", jsonrpc);
				ret.put("id", id);
				writeJSONObject(resp, ret);
			} catch (IOException e) {
				log.log(Level.WARNING, e.getMessage(), e);
				throw new RPCException(RPCException.INTERNAL_ERROR, e.getMessage(),  e);
			} catch (JSONException e) {
				log.log(Level.WARNING, e.getMessage(), e);
				throw new RPCException(RPCException.PARSE_ERROR, e.getMessage(), e);
			} catch (InstantiationException e) {
				log.log(Level.WARNING, e.getMessage(), e);
				throw new RPCException(RPCException.INTERNAL_ERROR, e.getMessage(), e);
			} catch (IllegalAccessException e) {
				log.log(Level.WARNING, e.getMessage(), e);
				throw new RPCException(RPCException.INTERNAL_ERROR, e.getMessage(), e);
			} catch (InvocationTargetException e) {
				log.log(Level.WARNING, e.getMessage(), e);
				throw new RPCException(RPCException.INTERNAL_ERROR, e.getMessage(), e);
			}
		} catch (RPCException ex) {
			JSONObject ret = new JSONObject();
			JSONObject jex = new JSONObject();
			try {
				String data = Utils.stack2string(ex);
				jex.put("code", ex.getErrorCode());
				jex.put("message", ex.getMessage());
				ret.put("data", data);
				ret.put("jsonrpc", jsonrpc);
				ret.put("id", id);
				writeJSONObject(resp, ret);
			} catch (Exception exx) {
				log.log(Level.WARNING, exx.getMessage(), exx);
			}
		} finally {
			request.set(null);
			response.set(null);
			appId.set(null);
			signature.set(null);
			arguments.set(null);
		}

	}

	/**
	 * Gets the request.
	 *
	 * @return the request
	 */
	public HttpServletRequest getRequest() {
		return request.get();
	}

	/**
	 * Gets the response.
	 *
	 * @return the response
	 */
	public HttpServletResponse getResponse() {
		return response.get();
	}

	/**
	 * Gets the app id.
	 *
	 * @return the app id
	 */
	protected String getAppId() {
		return appId.get();
	}

	/**
	 * Gets the signature.
	 *
	 * @return the signature
	 */
	protected String getSignature() {
		return signature.get();
	}

	/**
	 * Gets the arguments.
	 *
	 * @return the arguments
	 */
	protected JSONArray getArguments() {
		return arguments.get();
	}

	/**
	 * Check signature.
	 *
	 * @param key the key
	 * @return true, if successful
	 */
	protected boolean checkSignature(String key) {
		String str = key + getArguments().toString();

		String sig;
		try {
			sig = Utils.toHexString(str.getBytes("UTF-8"));

			return sig.equals(getSignature());
		} catch (UnsupportedEncodingException e) {
			log.info(e.getMessage());
		}

		return false;

	}

	/**
	 * Parses the json.
	 *
	 * @param request the request
	 * @return the jSON object
	 * @throws IOException Signals that an I/O exception has occurred.
	 * @throws JSONException the jSON exception
	 */
	private JSONObject parseJSON(HttpServletRequest request)
			throws IOException, JSONException {
		BufferedReader reader = null;
		char[] buff = new char[100];
		int sz = 0;
		try {
			reader = request.getReader();
			StringBuffer buffer = new StringBuffer();
			while ((sz = reader.read(buff)) != -1) {
				buffer.append(buff, 0, sz);
			}
			return new JSONObject(buffer.toString());
		} finally {
			if (reader != null)
				reader.close();
		}

	}

	/**
	 * Write json object.
	 *
	 * @param response the response
	 * @param jobj the jobj
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	private void writeJSONObject(HttpServletResponse response, JSONObject jobj)
			throws IOException {
		//response.setContentType("text/json; charset=utf-8");
		response.setContentType(ServiceProxy.JSON_CONTENT_TYPE);
		Writer writer = response.getWriter();
		writer.write(jobj.toString());
	}
	
	/**
	 * Before invoke.
	 *
	 * @param method the method
	 * @param args the args
	 * @throws RPCException the rPC exception
	 */
	public void beforeInvoke(Method method, Object[] args) throws RPCException{}
	
	/**
	 * After invoke.
	 *
	 * @param method the method
	 * @param args the args
	 * @param retObj the ret obj
	 * @throws RPCException the rPC exception
	 */
	public void afterInvoke(Method method, Object[] args, Object retObj)throws RPCException{}

	

}
